<html>

<head>
  <meta charset="utf-8">
  <title>mybatis小工具</title>
  <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.4/lodash.min.js"></script>
  <script src="graphql-parser.js"></script>
  <script>



    function displayDate() {
      var str1 = $("#t1").val();
      var str2 = $("#t2").val();
      var result1 = formatObject(str1);
      var result2 = formatContent(str2);
      var result = result1.map(item => {
        let obj = { name: item };
        obj.content = result2;
        return obj;
      });
      $("#result").text(JSON.stringify(result));
    }




    const beanTpl = `
package #{packageName}

import java.io.Serializable;

@Data
public class #{className} implements Serializable {
	#{fields}
}
`;

    const xmlTpl = `
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="#{packageName}">
	#{body}
</mapper>
`


const resultMapperTpl = `
<resultMap id="#{method}" type="#{bean}">
    #{fields}
  </resultMap>
`;


const mapperTpl = `
#{import}
public interface #{mapperName}{
  #{methods}
}
`;

const selectTpl = `
<select id="#{method}" resultMap="#{method}">
  #{sql}
</select>
`;

function toHump(name) {
    return name.replace(/\_(\w)/g, function(all, letter){
        return letter.toUpperCase();
    });
}
// 驼峰转换下划线
function toLine(name) {
  return name.replace(/([A-Z])/g,"_$1").toLowerCase();
}

function toHumpUpper(name) {
  return upper(toHump(name));
}

function upper(str){
 var strTemp = ""; //新字符串
 for(var i=0;i<str.length;i++){
  if(i == 0){
   strTemp += str[i].toUpperCase(); //第一个
   continue;
  }
  if(str[i] == " " && i< str.length-1){ //空格后
   strTemp += " ";
   strTemp += str[i+1].toUpperCase();
   i++;
   continue;
  }
  strTemp += str[i];
 }
  return strTemp;
 }


    
	function genSql(ast){
    
    let cols = [];

    function walkTree(tree,res){
      tree.properties.forEach(i=>{
        if(i.calls){
          let res = [];
          walkTree(i.properties,res)
        }else{
          res.push(`${i.alias}.${i.name}`)
        }
      })
    }

    ast.properties.forEach(i=>{
      if(i.calls){
        let res = [];
        walkTree(i.properties,res)
        cols.push(...res)
      }else{
        let name = `${i.alias}.${i.name}`
        cols.push(name);
      }
      
    })

    return render(selectTpl,{
      method: ast.call,
      sql:`select ${cols.join(',')} from ${ast.querybody}`
    })
	}
	

	function genBean(rootBean,res){
    let packageName = rootBean.package;
    let cName = rootBean.cName;
    let fields = []
    for(var bean of rootBean.fields){
      if(bean.fields){
        //生成关联类
        packageName=packageName+";\n"+`import ${bean.package}.${bean.cName};`;
        genBean(bean,res)
      }
      fields.push(`private ${bean.type} ${bean.name};`)
    }

    res.push(
      render(beanTpl,{
        packageName,
        className:cName,
        fields:fields.join("\n\n")
      })
    )
  }
	
	function genResultMapper(ast){
    let rows = [];
    ast.properties.forEach(prop=>{
      
      if(prop.calls){
        let srows = [];
        let config = toMap(prop.calls.find(i=>i.call=='config').parameters);
        if(config.fetch=='list'){
          srows.push(`<collection property="${prop.name}" ofType="${config.type}">`);
          srows.push(...genMapperRow(prop.properties.properties));
          srows.push(`</collection>`);
        }else{
          srows.push(`<association property="${prop.name}" javaType="${config.type}">`);
          srows.push(...genMapperRow(prop.properties.properties));
          srows.push(`</association>`)
        }
        rows.push(...srows);
      }else{
        let jname = prop.jname?prop.jname:toHump(prop.name)
        rows.push(`<result property="${jname}" column="${prop.alias}.${prop.name}"></result>`);
      }
    })
    return render(resultMapperTpl,{
        method:ast.call,
        bean:ast.parameters.find(i=>i.name=='type').value,
        fields:rows.join("\n")
      })
  }


  function genMapperRow(props){
    //TODO 是否能够嵌套？
    return props.map(prop=>{
      let jname = prop.jname?prop.jname:toHump(prop.name)
      return `<result property="${jname}" column="${prop.alias}.${prop.name}"></result>`;
    });
  }


  function genMapper(ast){
    let method = ast.call;
    let importPack = [];
    let fullName = ast.parameters.find(i=>i.name=='type').value;
    let {package,cName} = getClass(fullName);
    importPack.push('import '+fullName+';');
    let fetch = ast.parameters.find(i=>i.name=='fetch');
    let type = fetch?fetch.value:'obj';
    let jtype = type=='list'?`List<${cName}>`:cName
    if(type=='list'){
      importPack.push('import java.util.List;');
    }
    let mn = `${jtype} ${method}();`
    return render(mapperTpl,{
      mapperName:'xxMapper',
      methods:mn,
      import:importPack.join("\n")
    })
  }
  
  function getClass(fname){
    return {
      package:fname.substring(0,fname.lastIndexOf('.')),
      cName:fname.substring(fname.lastIndexOf('.')+1)
    }
  }
	
  
  function gen(ast){
    let fields = genProp(ast.properties)
    let rootFName = ast.parameters.find(i=>i.name=='type').value;
    let classInfo = getClass(rootFName)
    let rootBean = {...classInfo,fields}
    //生成rootbean
    return rootBean;
  }

  function genProp(props){
    return props.map(p=>{
      if(p.calls){
        return genSub(p);
      }
      return {
        name: p.jname?p.jname:toHump(p.name),
        tname: p.name,
        type: p.type
      }
    });
  }

  function toMap(parameters){
    return parameters.reduce((t,c)=>{
      t[c.name]=c.value
      return t;
    },{});
  }


  function genSub(subBean){
    let rootFName = subBean.calls[0].parameters.find(i=>i.name=='type').value;
    let config = subBean.calls[0].parameters.reduce((t,c)=>{
      t[c.name]=c.value
      return t;
    },{});

    let fetchType = config.fetch;
    let beanName = config.type;
    let {package,cName} = getClass(beanName)
    let humpName = toHump(cName)
    return {
      name: subBean.name,
      beanName,
      package,
      cName,
      fields:genProp(subBean.properties.properties),
      type: fetchType=='list'?`List<${cName}>`:cName
    }
  }



	
	
	
	function parseQl(v){
     return parser.parse(v);
	}


  function genCode(ast){
    let rootBean = gen(ast);
    let beans = [];
    genBean(rootBean,beans);
    let mapper = genMapper(ast)
    let resultMap = genResultMapper(ast)
    let sql = genSql(ast)
    return [mapper,resultMap,sql,beans.join("\n")]
  }



    function render(template, context) {

      var tokenReg = /(\\)?#\{([^\{\}\\]+)(\\)?\}/g;

      return template.replace(tokenReg, function (word, slash1, token, slash2) {
        if (slash1 || slash2) {
          return word.replace('\\', '');
        }

        var variables = token.replace(/\s/g, '').split('.');
        var currentObject = context;
        var i, length, variable;

        for (i = 0, length = variables.length, variable = variables[i]; i < length; ++i) {
          currentObject = currentObject[variable];
          if (currentObject === undefined || currentObject === null) return '';
        }

        return currentObject;
      })
    }


    $(function () {

      $('#button').click(function () {
        let v1 = $('#t1').val()
        let ast = parseQl(v1)
        console.log(ast)
        let res = genCode(ast)
        console.log(res)
         $('#t2').val(res.join("\n\n"))
      })

    })


  </script>
</head>

<body>

  <textarea id="t1" rows="20" cols="100"></textarea>
  <textarea id="t2" rows="20" cols="100"></textarea>
  <br />
  <button id="button" type="button">转换</button>
  <div id="result"></div>

</body>

</html>